/****************************************************************************

	COPYRIGHT(C) MAXSI SOFTWARE, JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010

    This file is part of Maxsi Release CMD.

    Maxsi Release CMD is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Maxsi Release CMD is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Maxsi Release CMD. If not, see <http://www.gnu.org/licenses/>.

	*/ #include "MaxsiReleaseCMDContributors.h" /*

	If you modify this file, please enter your name below and provide contact
	information in MaxsiReleaseCMDContributors.h. For more information please see
	MaxsiReleaseCMDContributors.h.
	
	Contributors to this file:

	- Jonas 'Sortie' Termansen
	- [your name here]

	MaxsiReleaseCMD.exe
	A command line utility that uploads product content to a Maxsi Distribution
	compatible content server and posts install scripts to the Master Server.

	Main.cpp
	The main console interface that does the heavy lifting

****************************************************************************/


#include "MaxsiEngine.h"
#include <direct.h>
#include <io.h>
#include <Share.h>
#include <SYS\Stat.h>
#include <fcntl.h>
#include <iostream> 
#include <string>
#include "Main.h"
#include "General.h"

#pragma warning(disable: 4244)

// INSTALLER DATA
char*	ContentArchive	=	0;
char*	MasterPath		=	0;
List	MasterServers	=	0;
List	ContentServers	=	0;
List	FileList		=	0;
__int64	TotalFileSize	=	0;
size_t	NumBlocks		=	0;
int		Flags			=	0;
List	BlockHashes		=	0;

MISFUNC(MIS_ContentArchive,MaxsiRelease){		ContentArchive	=		BuildString(1,In[0]);	return	0;}
MISFUNC(MIS_MasterPath,MaxsiRelease){			MasterPath		=		BuildString(1,In[0]);	return	0;}
MISFUNC(MIS_MasterServer,MaxsiRelease)	{		MasterServers	=		AddListEntry(MasterServers,In[0],strlen(In[0])+1);	return 0; }
MISFUNC(MIS_ContentServer,MaxsiRelease)	{		ContentServers	=		AddListEntry(ContentServers,In[0],strlen(In[0])+1);	return 0; }
MISFUNC(MIS_Flags,MaxsiRelease)			{		Flags			=		atoi(In[0]);	return 0; }

#define STEP_UPLOAD_CONTENT_SERVER		(0x00000001)
#define STEP_GENERATE_INSTALL_SCRIPT	(0x00000002)


int	PrintToConsole(char* Line)
{
	return printf("%s\n",Line);
}

int	GetConsoleKey()
{
	char	Key;
	fgets(&Key,1,stdin);
	return	TRUE;
}

int main(int argc, char ** argv)
{
	PrintToConsole(Product_Version_Info);
	PrintToConsole("");

	if ( argc == 1 )
	{
		PrintToConsole("Usage: MaxsiReleaseCMD.exe <.mis script filepath>");
		GetConsoleKey();
	}
	else
	{
		if ( RunMISScript(argv[1]) )
		{
			char*	NumMirrors	=	UIToA((unsigned int)GetListSize(ContentServers));
			char*	Status		=	0;

			if ( GetListSize(ContentServers) == 1 )
			{
				Status		=	BuildString(5,"Releasing ", ContentArchive, " to ", NumMirrors, " Content Servers");
			}
			else
			{
				Status		=	BuildString(5,"Releasing ", ContentArchive, " to ", NumMirrors, " Content Servers");
			}

			PrintToConsole(Status);
			PrintToConsole("");
			delete[] Status;

			Release();
			
			PrintToConsole("Done... Press Enter to close the application.");
			GetConsoleKey();			
		}
		else
		{
			GetConsoleKey();
		}
	}

	if ( ContentArchive )	{ delete[] ContentArchive; }
	if ( MasterPath )		{ delete[] MasterPath; }
	if ( MasterServers )	{ delete[] MasterServers; }
	if ( ContentServers )	{ DeleteList(ContentServers); }
	if ( FileList )			{ DeleteList(FileList); }

	return 0;
}


bool	RunMISScript(char* File)
{
	// Load the install script!
	int		fh		=	0;
	bool	Result	=	false;

	_sopen_s(&fh,File, O_BINARY | O_RDONLY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
	
	if (fh  == -1)
	{
		char* Error = BuildString(3,"MaxsiReleaseCMD couldn't open ",File,"\n\nPlease make sure it exists and can be read.");
		PrintToConsole(Error);
		delete[] Error;
	}
	else
	{
		int FileSize = _filelength(fh);

		char* AllocatedBuffer = new char[FileSize+1];

		_read(fh, AllocatedBuffer, (unsigned int)FileSize);
		AllocatedBuffer[FileSize]	=	0;
		_close(fh);

		MISRun(AllocatedBuffer,"MaxsiRelease");
		delete AllocatedBuffer;

		Result	=	true;
	}

	return Result;
}

bool Release()
{
	bool	Result		=	false;

	if ( GatherMasterPathInformation(MasterPath,"/") )
	{
		PrintToConsole("");
		Result	=	true;

		if ( TotalFileSize % MaxsiTorrent_BlockSize == 0 )
		{
			NumBlocks	=	TotalFileSize/MaxsiTorrent_BlockSize;
		}
		else
		{
			NumBlocks	=	TotalFileSize/MaxsiTorrent_BlockSize+1;
		}

		char*	strNumBlocks	=	UIToA((unsigned int)NumBlocks);

		if ( Flags & STEP_UPLOAD_CONTENT_SERVER )
		{	
			for (size_t I = 0; I < GetListSize(ContentServers); I++)
			{
				char*	Status			=	BuildString(4,"Uploading ",strNumBlocks,"x1MiB Blocks to ",GetListEntry(ContentServers,(unsigned int)I));
				PrintToConsole(Status);
				delete[] Status;
				
				UploadBlocksToHost((char*)GetListEntry(ContentServers,(unsigned int)I));
			}
		}
		else if ( Flags & STEP_GENERATE_INSTALL_SCRIPT )
		{
			char*	Status			=	BuildString(3,"SHA-1 Hashing ",strNumBlocks,"x1MiB Blocks");
			PrintToConsole(Status);
			delete[] Status;
			UploadBlocksToHost(NULL);
		}
		if ( Flags & STEP_GENERATE_INSTALL_SCRIPT )
		{	
			GenerateInstallScript();
		}

		delete[] strNumBlocks;
	}
	else
	{
		PrintToConsole("Error Gathering Master Path Information... Please check paths.");
	}

	

	return	Result;
}

bool	UploadBlocksToHost(char* Host)
{
	size_t	Filled		=	0;
	size_t	Empty		=	MaxsiTorrent_BlockSize;
	size_t	Read		=	0;
	size_t	Left		=	0;
	size_t	Possible	=	0;
	char*	Raw			=	new char[MaxsiTorrent_BlockSize];
	char*	Com			=	new char[MaxsiTorrent_BlockSize];
	size_t	BlockId		=	0;
	bool	IsHashing	=	BlockHashes==NULL;

	if ( IsHashing )
	{
		BlockHashes		=	CreateList(NumBlocks);
	}

	for ( size_t I = 0; I < GetListSize(FileList); I++)
	{
		char*	FilePath	=	BuildString(2,MasterPath,GetListEntry(FileList,I));

		int fh = 0;							
		_sopen_s(&fh,FilePath,O_BINARY | O_RDONLY | O_SEQUENTIAL,_SH_DENYNO,_S_IREAD | _S_IWRITE);

		if (fh != -1)
		{
			Read	=	0;
			Left	=	_filelength(fh);

			while ( Left )
			{
				if ( Empty > Left)
				{
					Possible	=	Left;
				}
				else
				{
					Possible	=	Empty;
				}
				
				_read(fh,Raw+Filled,Possible);

				Filled	+=	Possible;
				Empty	-=	Possible;
				Left	-=	Possible;
				Read	+=	Possible;

				if ( Empty == 0 || (I == GetListSize(FileList)-1 && Left == 0))
				{
					// The buffer is now filled. Compress it!

					if ( IsHashing )
					{
						char*		Hash	=	CalculateSHA1(Raw,Filled);
						SetListEntry(BlockHashes,(unsigned int)BlockId,Hash,strlen(Hash)+1);
						delete[]	Hash;
					}

					// Do we have a host, or are we only hashing?

					if ( Host )
					{
						// Compress the buffer!
						
						if ( sizeof(z_stream_s)+compressBound(MaxsiTorrent_BlockSize) < MaxsiTorrent_BlockSize )
						{
							// Shit! The buffer is too small!
							ME_UNDEFINED_BEHAVIOR("The compressed buffer was not large enough for the compressed data.");
						}

						uLong	ComSize		=	MaxsiTorrent_BlockSize;
						int		Result		=	compress2((Bytef*)Com,&ComSize,(Bytef*)Raw,Filled,9);
							
						if ( Result == Z_OK )
						{
							UploadBlockToHost(Host,&Com,ComSize,BlockId);
							BlockId++;
						}
						else
						{
							return false;
						}

					}
					else
					{
						BlockId++;
						if ( BlockId % 100 == 0 )
						{
							char*	strBlockId	=	UIToA((unsigned int)BlockId);
							char*	Status			=	BuildString(2,"SHA Calculated Block #",strBlockId);
							PrintToConsole(Status);
							delete[] Status;
							delete[] strBlockId;
						}
					}
					
					// The buffer is now cleared
					Empty	=	MaxsiTorrent_BlockSize;
					Filled	=	0;
				}
			}

			_close(fh);
		}
		else
		{
			return false;
		}

		delete[]	FilePath;

	}

	delete[] Raw;
	delete[] Com;

	return true;
}

bool	GenerateInstallScript()
{
	PrintToConsole("Generating Install Script...");
	#define INSTALL_SCRIPT_SIZE 8192
	#define InstallScriptCat(Text) if ( strlen(Text) < ScriptLeft ) { strcat_s(Script,INSTALL_SCRIPT_SIZE,Text); ScriptLeft-=strlen(Text); } else { AddListEntry(Scripts,Script,strlen(Script)+1); Script[0] = 0; ScriptLeft = INSTALL_SCRIPT_SIZE; strcat_s(Script,INSTALL_SCRIPT_SIZE,Text); ScriptLeft-=strlen(Text); }

	List	Scripts		=	CreateList(0);
	char*	Script		=	new char[INSTALL_SCRIPT_SIZE];
			Script[0]	=	0;
	size_t	ScriptLeft	=	INSTALL_SCRIPT_SIZE;

	char*	InstallScriptHeader = BuildString(1,"// Generated by MaxsiReleaseCMD on " __DATE__ "\r\nApplication/x-MaxsiEngineInstallerScript/2.0\r\nApplication: x-MaxsiEngineMaxsiInstaller\r\n\r\n");
	InstallScriptCat(InstallScriptHeader);
	delete[] InstallScriptHeader;

	for (size_t I = 0; I < GetListSize(ContentServers); I++)
	{
		InstallScriptCat("Server \"");
		InstallScriptCat((char*)GetListEntry(ContentServers,I));
		InstallScriptCat("\" MaxsiDistribution\r\n");
	}

	InstallScriptCat("\r\n");

	for ( size_t I = 0; I < GetListSize(FileList); I++)
	{
		PrintToConsole((char*)GetListEntry(FileList,I));

		char*	FilePath	=	BuildString(2,MasterPath,GetListEntry(FileList,I));

		int fh = 0;							
		_sopen_s(&fh,FilePath,O_BINARY | O_RDONLY | O_SEQUENTIAL,_SH_DENYNO,_S_IREAD | _S_IWRITE);

		long	FileLen	=	_filelength(fh);
		char*	Buffer	=	new char[FileLen];

		_read(fh,Buffer,FileLen);

		char*	Sha1	=	CalculateSHA1(Buffer,FileLen);
		char*	Size	=	UIToA((unsigned int)FileLen);

		InstallScriptCat("File \"");
		InstallScriptCat((char*)GetListEntry(FileList,I));
		InstallScriptCat("\" ");
		InstallScriptCat(Size);
		InstallScriptCat(" ");
		InstallScriptCat(Sha1);
		InstallScriptCat("\r\n");
		
		delete[]	Sha1;
		delete[]	Buffer;
		delete[]	Size;

		delete[]	FilePath;

	}
	for ( size_t I = 0; I < GetListSize(BlockHashes); I++)
	{
		char*	Number		=	UIToA((unsigned int)I+1);

		InstallScriptCat("BlockHash ");
		InstallScriptCat(Number);
		InstallScriptCat(" ");
		InstallScriptCat((char*)GetListEntry(BlockHashes,I));
		InstallScriptCat("\r\n");

		delete[] Number;
	}

	size_t	Total	=	0;

	if ( GetListSize(Scripts) )
	{
		for (size_t I = 0; I < GetListSize(Scripts); I++)
		{
			Total	+=	GetListEntrySize(Scripts,I);		
		}
	}
	Total	+=	strlen(Script);

	char*	FinalScript		=	new char[Total+1];
	FinalScript[0]		=	0;

	if ( GetListSize(Scripts) )
		{
		for (size_t I = 0; I < GetListSize(Scripts); I++)
		{
			strcat_s(FinalScript,Total+1,(char*)GetListEntry(Scripts,I));	
		}
	}
	strcat_s(FinalScript,Total+1,Script);	

	PrintToConsole("Uploading Install Script...");

	for (size_t I = 0; I < GetListSize(ContentServers); I++)
	{
		char*	HostName			=	GetHostFromURI((char*)GetListEntry(MasterServers,I));
		char*	Resource			=	GetResourceFromURI((char*)GetListEntry(MasterServers,I));
		char*	ContentArchiveEnc	=	EncodeURL(ContentArchive);
		char*	FinalURI			=	BuildString(3, Resource,"?action=uploadinstallscript&contentarchive=",ContentArchiveEnc);
	
		char*	Status				=	BuildString(2,"Uploading Install Script to ",GetListEntry(MasterServers,(unsigned int)I));
		PrintToConsole(Status);
		delete[] Status;

		HTTPParser_Struct	Reply;
		Reply.In			=	0;
		Reply.ResponseCode	=	0;


		while	(Reply.In==0)
		{
			if ( Reply.ResponseCode && Reply.ResponseCode != 200 )
			{
				char*	ResponseCode	=	UIToA((unsigned int)Reply.ResponseCode);
				char*	Error			=	BuildString(3,"Recieved HTTP Response Code ",ResponseCode,"! Install Script might not have been uploaded.");
				PrintToConsole(Error);
				delete[]	Error;
				delete[]	ResponseCode;
			}

			Reply = UploadFile(HostName,FinalURI,"temp",FinalScript,strlen(FinalScript));
			DeleteHTTPParser_Struct(Reply);
		}

		delete[]	ContentArchiveEnc;
		delete[]	FinalURI;
		delete[]	Resource;
		delete[]	HostName;
	}

	delete[]	FinalScript;
	delete[]	Script;
	DeleteList(Scripts);

	return true;
}

bool	UploadBlockToHost(char* Host, char** Com,uLong ComSize,size_t BlockId)
{
	
	char*	strNumBlocks	=	UIToA((unsigned int)NumBlocks);
	char*	strBlockId		=	UIToA((unsigned int)BlockId+1);
	char*	Status			=	BuildString(6,"Uploading 1MiB Block #",strBlockId,"/",strNumBlocks," to ",Host);
	delete[] strBlockId;
			strBlockId		=	UIToA((unsigned int)BlockId);	
	char*	FileName		=	BuildString(4,ContentArchive,"_Block_",strBlockId,".MaxsiDistribution");
	char*	HostName		=	GetHostFromURI(Host);
	char*	Resource		=	GetResourceFromURI(Host);
	PrintToConsole(Status);
	delete[] Status;
	delete[] strNumBlocks;

	HTTPParser_Struct	Reply;
	Reply.In			=	0;
	Reply.ResponseCode	=	0;

	char*	ContentArchiveEnc	=	EncodeURL(ContentArchive);

	while	(Reply.In==0)
	{
		if ( Reply.ResponseCode && Reply.ResponseCode != 200 )
		{
			char*	ResponseCode	=	UIToA((unsigned int)Reply.ResponseCode);
			char*	Error			=	BuildString(3,"Recieved HTTP Response Code ",ResponseCode,"! Block might not have been uploaded.");
			PrintToConsole(Error);
			delete[]	Error;
			delete[]	ResponseCode;
		}
		char*	FinalResource		=	BuildString(5,Resource,"?action=upload&contentarchive=",ContentArchiveEnc,"&blockid=",strBlockId);;
		Reply = UploadFile(HostName,FinalResource,FileName,*Com,ComSize);

		ME_DONT_COMPILE_RELEASE_BUILD();

		// TODO: WE MUST CHECK IF THE RETURN VALUE IS SUCCESS OR FAILURE!
		
		delete[]	FinalResource;
		delete[]	ContentArchiveEnc;

		DeleteHTTPParser_Struct(Reply);
	}

	delete[] HostName;
	delete[] Resource;
	delete[] FileName;
	delete[] strBlockId;

	return true;
}

bool	GatherMasterPathInformation(char*	Path, char* Ex)
{
	char*	Status	=	BuildString(3,"Seeking ", Ex, "...");
	PrintToConsole(Status);
	delete[] Status;

	struct			__finddata64_t File;
	char*			SeekPath	=	BuildString(3,Path,Ex,"*");
	intptr_t		FileHandle;
	bool			Result		=	true;

	for (char K = 0; K<2; K++)
	{
		if ( (FileHandle = _findfirst64( SeekPath, &File)) != -1L )
		{
			do
			{
				if (Result && K == 1 && File.attrib & _A_SUBDIR)
				{
					if ( strcmp(File.name,".") != 0	&& strcmp(File.name,"..") != 0)
					{
						char*	NewEx	=	BuildString(3,Ex,File.name,"/");
						if (!GatherMasterPathInformation(Path,NewEx)) { Result = false; }					
					}
				}
				if (Result && K == 0 && !(File.attrib & _A_SUBDIR) )
				{
					char*		FilePath	=	BuildString(2,Ex,File.name);
								FileList	=	AddListEntry(FileList,FilePath,strlen(FilePath)+1);
					delete[]	FilePath;

					
							FilePath	=	BuildString(3,Path,Ex,File.name);

					int fh = 0;							
					_sopen_s(&fh,FilePath,O_BINARY | O_RDONLY,_SH_DENYNO,_S_IREAD | _S_IWRITE);

					if (fh != -1)
					{				
						TotalFileSize += _filelength(fh);
						_close(fh);
					}

					delete[]	FilePath;

				}
			}
			while ( _findnext64( FileHandle, &File ) == 0 );
		}
		else
		{
			Result	=	false;
		}
		_findclose( FileHandle );
	}

	delete[] SeekPath;

	return Result;
}